package rexsee.network;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import rexsee.core.browser.Browser;
import rexsee.core.browser.clazz.JavascriptInterface;
import rexsee.network.SocketAbstractClass.SocketConnectedListener;
import rexsee.network.SocketAbstractClass.SocketDataListener;
import rexsee.network.SocketAbstractClass.SocketFailedListener;
import rexsee.network.SocketAbstractClass.SocketLostListener;
import rexsee.network.SocketAbstractClass.SocketStartedListener;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Handler;

public class RexseeInstantAudio implements JavascriptInterface {

	public static final String INTERFACE_NAME = "InstantAudio";
	@Override
	public String getInterfaceName() {
		return mBrowser.application.resources.prefix + INTERFACE_NAME;
	}
	@Override
	public JavascriptInterface getInheritInterface(Browser childBrowser) {
		return this;
	}
	@Override
	public JavascriptInterface getNewInterface(Browser childBrowser) {
		return new RexseeSocket(childBrowser);
	}

	private final SocketStartedListener onStarted = new SocketStartedListener() {
		@Override
		public void run(String id, boolean isServer) {
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_STARTED, new String[]{id, String.valueOf(isServer)});
		}
	};
	private final SocketFailedListener onFailed = new SocketFailedListener() {
		@Override
		public void run(String id, boolean isServer, String error) {
			stop();
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_FAILED, new String[]{id, String.valueOf(isServer), error});
		}
	};
	private final SocketConnectedListener onConnected = new SocketConnectedListener() {
		@Override
		public void run(String id, final long index) {
			final Socket mSocket = (index != -1) ? mServer.getSocket(index) : mClient.getSocket();
			boolean mAllowRecord = (index != -1) ? mServerAllowRecord : mClientAllowRecord;
			boolean mAllowPlayback = (index != -1) ? mServerAllowPlayback : mClientAllowPlayback;
			if (mSocket == null || !mSocket.isConnected()) {
				if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Socket is not ready.");
			} else {
				if (mAllowRecord) {
					mRecordThread = new Thread() {
						@Override
						public void run() {
							try {
								if (mAudioRecord != null) {
									mAudioRecord.stop();
									mAudioRecord.release();
									mAudioRecord = null;
								}
								mAudioRecord = new AudioRecord(mAuidoSource, mSampleRateInHz, mChannelConfig, mAudioFormat, mRecordBufferSizeInByte);
								mAudioRecord.startRecording();
								final Handler mHandler = (index != -1) ? mServerHandler : mClientHandler;
								mHandler.postDelayed(new Runnable() {
									@Override
									public void run() {
										try {
											byte[] bytes = new byte[mBufferSizeEachTime];
											int length = mAudioRecord.read(bytes, 0, bytes.length);
											if (length > 0) {
												try {
													mSocket.getOutputStream().write(bytes, 0, length);
												} catch (IOException e) {
												}
											}
											mHandler.postDelayed(this, mRecordingCycle);
										} catch (Exception e) {
										}
									}
								}, mRecordingCycle);
								/*
								while (mAudioRecord != null && mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
									byte[] bytes = new byte[mBufferSizeEachTime];
									int length = mAudioRecord.read(bytes, 0, bytes.length);
									if (length > 0) {
										try {
											mSocket.getOutputStream().write(bytes, 0, length);
										} catch (IOException e) {
										}
									}
								}
								*/
							} catch (Exception e) {
								if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
							}
						}
					};
					mRecordThread.start();
				} else {
					mAudioRecord = null;
					mRecordThread = null;
				}
				if (mAllowPlayback) {
					try {
						if (mAudioTrack != null) {
							mAudioTrack.stop();
							mAudioTrack.release();
							mAudioTrack = null;
						}
						mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig, mAudioFormat, mPalybackBufferSizeInByte, AudioTrack.MODE_STREAM);
						mAudioTrack.setStereoVolume(0.9f, 0.9f);
						mAudioTrack.play();
					} catch (Exception e) {
						if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
					}
				} else {
					mAudioTrack = null;
				}
			}
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_CONNECTED, new String[]{id, String.valueOf(index)});
		}
	};
	private final SocketLostListener onLost = new SocketLostListener() {
		@Override
		public void run(String id, long index, String message) {
			stop();
			if (mBrowser != null) mBrowser.eventList.run(SocketAbstractClass.EVENT_SOCKET_LOST, new String[]{id, String.valueOf(index), message});
		}
	};
	private final SocketDataListener onRead = new SocketDataListener() {
		@Override
		public void run(String id, long index, byte[] bytes) {
			try {
				boolean mAllowPlayback = (index != -1) ? mServerAllowPlayback : mClientAllowPlayback;
				if (mAllowPlayback && mAudioTrack != null) mAudioTrack.write(bytes, 0, bytes.length);
			} catch (Exception e) {
			}
		}
	};
	private final SocketDataListener onWrite = null;

	private final String mServerId = "InstantAudioServer";
	private Handler mServerHandler = null;
	private final String mClientId = "InstantAudioClient";
	private Handler mClientHandler = null;
	private final int mServerPort = 8081;
	private final int mClientPort = 8082;
	private final int mConnectTimeout = 10000;
	private final int mRecordingCycle = 20;
	private final int mReadingCycle = 20;

	private final int mAuidoSource = MediaRecorder.AudioSource.MIC;
	private final int mSampleRateInHz = 22050; // 44100, 22050 and 11025
	private final int mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
	private final int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
	private final int mStreamType = AudioManager.STREAM_MUSIC;

	private final int mRecordBufferSizeInByte;
	private final int mPalybackBufferSizeInByte;
	private final int mBufferSizeEachTime;

	private final Browser mBrowser;
	private SocketServer mServer = null;
	private boolean mServerAllowRecord = true;
	private boolean mServerAllowPlayback = false;
	private SocketClient mClient = null;
	private boolean mClientAllowRecord = true;
	private boolean mClientAllowPlayback = false;
	private Thread mRecordThread = null;
	private AudioRecord mAudioRecord = null;
	private AudioTrack mAudioTrack = null;

	public RexseeInstantAudio(Browser browser) {
		mBrowser = browser;
		mRecordBufferSizeInByte = mBufferSizeEachTime = getRecordMinBufferSize();
		mPalybackBufferSizeInByte = getPalybackMinBufferSize();
	}

	//JavaScript Interface

	public int getRecordMinBufferSize() {
		return AudioRecord.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
	}
	public int getPalybackMinBufferSize() {
		return AudioTrack.getMinBufferSize(mSampleRateInHz, mChannelConfig, mAudioFormat);
	}

	public boolean startServer(boolean allowRecord, boolean allowPlayback) {
		mServerAllowRecord = allowRecord;
		mServerAllowPlayback = allowPlayback;
		InetAddress addr = RexseeNetwork.getInetAddress();
		if (addr == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Local address is not available.");
			return false;
		}
		InetSocketAddress localAddress = new InetSocketAddress(addr, mServerPort);
		try {
			if (mServer != null) {
				mServer.close();
				mServer = null;
			}
			mServer = new SocketServer(mServerId, localAddress, 1, -1, mReadingCycle);
			mServer.setListeners(onStarted, onFailed, onConnected, onLost, onRead, onWrite);
			mServerHandler = new Handler();
			mServer.start();
			return true;
		} catch (Exception e) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
			return false;
		}
	}
	public boolean startClient(String remoteIp, boolean allowRecord, boolean allowPlayback) {
		mClientAllowRecord = allowRecord;
		mClientAllowPlayback = allowPlayback;
		InetAddress addr = RexseeNetwork.getInetAddress();
		if (addr == null) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Local address is not available.");
			return false;
		}
		InetSocketAddress localAddress = new InetSocketAddress(addr, mClientPort);
		InetSocketAddress remoteAddress;
		try {
			remoteAddress = new InetSocketAddress(InetAddress.getByName(remoteIp), mServerPort);
		} catch (UnknownHostException e) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), "Remote address is not available.");
			return false;
		}
		try {
			if (mClient != null) {
				mClient.close();
				mClient = null;
			}
			mClient = new SocketClient(mClientId, localAddress, remoteAddress, mConnectTimeout, mReadingCycle);
			mClient.setListeners(onStarted, onFailed, onConnected, onLost, onRead, onWrite);
			mClientHandler = new Handler();
			mClient.start();
			return true;
		} catch (Exception e) {
			if (mBrowser != null) mBrowser.exception(getInterfaceName(), e);
			return false;
		}
	}

	public void stop() {
		try {
			if (mServer != null) {
				mServer.close();
				mServer = null;
			}
		} catch (Exception e) {
		}
		try {
			if (mClient != null) {
				mClient.close();
				mClient = null;
			}
		} catch (Exception e) {
		}
		try {
			if (mRecordThread != null) {
				mRecordThread = null;
			}
		} catch (Exception e) {
		}
		try {
			if (mAudioRecord != null) {
				mAudioRecord.stop();
				mAudioRecord.release();
				mAudioRecord = null;
			}
		} catch (Exception e) {
		}
		try {
			if (mAudioTrack != null) {
				mAudioTrack.stop();
				mAudioTrack.release();
				mAudioTrack = null;
			}
		} catch (Exception e) {
		}
	}

	/*
	public int loop = 0;
	public boolean setAudioRecorder() {
		if (mAudioRecord != null) {
			mAudioRecord.stop();
			mAudioRecord.release();
			mAudioRecord = null;
		}
		mAudioRecord = new AudioRecord(mAuidoSource, mSampleRateInHz, mChannelConfig, mAudioFormat, mRecordBufferSizeInByte);
		loop = 0;
		while (loop < 300 && mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
			loop++;
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
			}
		}
		return (loop < 300) ? true : false;
	}
	public boolean setAudioTrack() {
		if (mAudioTrack != null) {
			mAudioTrack.stop();
			mAudioTrack.release();
			mAudioTrack = null;
		}
		mAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz, mChannelConfig, mAudioFormat, mPalybackBufferSizeInByte, AudioTrack.MODE_STREAM);
		loop = 0;
		while (loop < 300 && mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
			loop++;
			try {
				Thread.sleep(20);
			} catch (InterruptedException e) {
			}
		}
		return (loop < 300) ? true : false;
	}
	*/

}
